#include "parsers.h"
#include "HelperFunctions.h"
#include "SymbolTable.h"
#include "Stack.h"

Stack<SymbolTable> symbolTables;

void parse_COMMAND();
void parse_COMMANDS();
eType parse_CONDITION();
void parse_DEFINITION();
eType parse_DEFINITION_i(SymbolTableEntry *i_Entry);
eKind parse_DEFINITION_ii(SymbolTableEntry *i_Entry, eType i_Type);
void parse_DEFINITIONS();
void parse_DEFINITIONS_i();
eType parse_EXPRESSION();
eType parse_EXPRESSION_i(eType i_Type);
eType parse_TYPE();

void parse_PROGRAM()
{
	eTokenType followList[] = { EOF_Token, none_Token };

	if (match(main_Token, followList))
	{
		symbolTables.Push(new SymbolTable(NULL));
		outputRule(1);
		parse_DEFINITIONS();
		if (match(begin_Token, followList))
		{
			parse_COMMANDS();
			if (match(end_Token, followList))
			{
				match(EOF_Token, NULL);
			}
		}

		delete symbolTables.Pop();
	}
}

void parse_DEFINITIONS()
{
	outputRule(2);
	parse_DEFINITION();
	parse_DEFINITIONS_i();
}

void parse_DEFINITIONS_i()
{
	eTokenType followList[] = { begin_Token, start_Token, none_Token };
	Token *t = nextToken();

	if (t->type == semicolon_Token)
	{
		outputRule(3);
		parse_DEFINITION();
		parse_DEFINITIONS_i();
	}
	else if (existsInFollow(t->type, followList))
	{
		outputRule(4);
		backToken();
	}
	else
	{
		outputLexicalError(t, followList);
		gotoFollow(followList);
	}
}

void parse_DEFINITION()
{
	eTokenType followList[] = { semicolon_Token, begin_Token, start_Token, none_Token };
	Token *t = nextToken();

	if (t->type == id_Token)
	{
		if (match(colon_Token, followList))
		{
			SymbolTableEntry *entry = symbolTables.Peek()->Add(t);

			// Only parse this Variable if an entry was created.
			if(!isRedefined(entry, t))
			{
				outputRule(5);
				entry->SetType(parse_DEFINITION_i(entry));
			}
			else
			{
				gotoFollow(followList);
			}
		}
	}
	else
	{
		eTokenType expectedTokenTypes[] = { id_Token, none_Token };

		outputLexicalError(t, expectedTokenTypes);
		gotoFollow(followList);
	}
}

eType parse_DEFINITION_i(SymbolTableEntry *i_Entry)
{
	eTokenType followList[] = { semicolon_Token, begin_Token, start_Token, none_Token };
	Token *t = nextToken();
	eType type = none_Type;

	if (t->type == exception_Token)
	{
		outputRule(6);
		i_Entry->SetKind(exception_Kind);
	}
	else if (attemptRepairKeyword(t, exception_Token))
	{
		eTokenType expectedTokenTypes[] = { exception_Token, none_Token };

		backToken();
		outputLexicalWarning(t, expectedTokenTypes);
		type = parse_DEFINITION_i(i_Entry);
	}
	else
	{
		outputRule(7);
		backToken();
		type = parse_TYPE();
		i_Entry->SetKind(parse_DEFINITION_ii(i_Entry, type));
	}

	return type;
}

eKind parse_DEFINITION_ii(SymbolTableEntry *i_Entry, eType i_Type)
{
	eTokenType followList[] = { semicolon_Token, begin_Token, start_Token, none_Token };
	Token *t = nextToken();
	eKind kind = variable_Kind;

	if (t->type == const_Token)
	{
		kind = constant_Kind;
		outputRule(8);
		if (match(assign_Token, followList))
		{
			if (match(number_Token, followList))
			{
				eType numType;

				t = currentToken();
				numType = interpretNumericType(t->lexeme);
				matchType(i_Type, numType, i_Entry->GetToken());
			}
		}
	}
	else if (attemptRepairKeyword(t, const_Token))
	{
		eTokenType expectedTokenTypes[] = { const_Token, none_Token };

		backToken();
		outputLexicalWarning(t, expectedTokenTypes);
		kind = parse_DEFINITION_ii(i_Entry, i_Type);
	}
	else if (existsInFollow(t->type, followList))
	{
		outputRule(9);
		backToken();
	}
	else
	{
		outputLexicalError(t, followList);
		gotoFollow(followList);
	}

	return kind;
}

eType parse_TYPE()
{
	eTokenType followList[] = { const_Token, semicolon_Token, begin_Token, start_Token, none_Token };
	eTokenType expectedTokenTypes[] = { integer_Token, real_Token, none_Token };
	Token *t = nextToken();
	eType type = none_Type;

	switch(t->type)
	{
	case integer_Token:
		outputRule(10);
		type = integer_Type;
		break;
	case real_Token:
		outputRule(11);
		type = real_Type;
		break;
	default:
		if (attemptRepairKeyword(t, integer_Token) || attemptRepairKeyword(t, real_Token))
		{
			backToken();
			outputLexicalWarning(t, expectedTokenTypes);
			type = parse_TYPE();
		}
		else
		{
			outputLexicalError(t, expectedTokenTypes);
			gotoFollow(followList);
		}

		break;
	}

	return type;
}

void parse_COMMANDS()
{
	eTokenType followList[] = { end_Token, else_Token, fi_Token, until_Token, finish_Token, none_Token };
	Token *t = nextToken();
	
	backToken();
	if (!existsInFollow(t->type, followList))
	{
		outputRule(12);
		parse_COMMAND();
		if (match(semicolon_Token, followList))
		{
			parse_COMMANDS();
		}
	}
	else
	{
		outputRule(13);
	}
}

void parse_COMMAND()
{
	eTokenType expectedTokenTypes[] = { id_Token, if_Token, do_Token, throw_Token, none_Token };
	eTokenType followList[] = { semicolon_Token, none_Token };
	Token *t = nextToken();

	switch(t->type)
	{
	case id_Token:
		outputRule(14);
		if (match(assign_Token, followList))
		{
			SymbolTableEntry *entry = symbolTables.Peek()->Find(t->lexeme);
			eType type = parse_EXPRESSION();

			if (isDefined(entry, t))
			{
				if (matchKinds(variable_Kind, entry->GetKind()))
				{
					matchType(entry->GetType(), type, t);
				}
				else
				{
					char buffer[1000] = { '\0' };

					sprintf(buffer, "Semantic Kind Mismatch Error: You cannot assign to a '%s', at [%d, %d] (line, column).",
						kindStrings[entry->GetKind()] ,t->lineNumber, t->columnNumber);
					outputLine(buffer);
				}
			}
		}
		break;
	case if_Token:
		outputRule(15);
		matchType(boolean_Type, parse_CONDITION(), t);
		if (match(then_Token, followList))
		{
			parse_COMMANDS();
			if (match(else_Token, followList))
			{
				parse_COMMANDS();
				match(fi_Token, followList);
			}
		}
		break;
	case do_Token:
		outputRule(16);
		parse_COMMANDS();
		if (match(until_Token, followList))
		{			
			parse_CONDITION();
			match(od_Token, followList);
		}
		break;
	case throw_Token:
		outputRule(17);
		if (match(id_Token, followList))
		{
			SymbolTableEntry *entry;

			t = currentToken();
			entry = symbolTables.Peek()->Find(t->lexeme);
			if (isDefined(entry, t))
			{
				if (!matchKinds(exception_Kind, entry->GetKind()))
				{
					char buffer[1000] = { '\0' };

					sprintf(buffer, "Semantic Kind Mismatch Error: Cannot throw '%s' since it is not an exception, at [%d, %d] (line, column).",
						t->lexeme, t->lineNumber, t->columnNumber);
					outputLine(buffer);
				}
			}
		}
		break;
	case block_Token:
		symbolTables.Push(new SymbolTable(symbolTables.Peek()));
		outputRule(18);
		parse_DEFINITIONS();
		if (match(start_Token, followList))
		{
			parse_COMMANDS();
			match(finish_Token, followList);
		}

		delete symbolTables.Pop();
		break;
	default:
		if (attemptRepairKeyword(t, if_Token) || attemptRepairKeyword(t, do_Token) ||
			attemptRepairKeyword(t, throw_Token) || attemptRepairKeyword(t, block_Token))
		{
			eTokenType expectedTokenTypes[] = { if_Token, do_Token, throw_Token, block_Token, none_Token };

			backToken();
			outputLexicalWarning(t, expectedTokenTypes);
			parse_COMMAND();
		}
		else
		{
			outputLexicalError(t, expectedTokenTypes);
			gotoFollow(followList);
		}

		break;
	}
}

eType parse_EXPRESSION()
{
	eTokenType followList[] = { rel_op_Token, then_Token, od_Token, rightbracket_Token, ar_op_Token, 
		semicolon_Token, none_Token };
	eTokenType expectedTokenTypes[] = { number_Token, id_Token, leftbracket_Token, none_Token };
	Token *t = nextToken();
	eType type = none_Type;

	switch(t->type)
	{
	case number_Token:
		{
			eType numType = interpretNumericType(t->lexeme);
			
			outputRule(19);
			type = parse_EXPRESSION_i(numType);
		}

		break;
	case id_Token:
		{
			SymbolTableEntry *entry = symbolTables.Peek()->Find(t->lexeme);
			eKind kind = isDefined(entry, t) ? entry->GetKind() : none_Kind;
			type = entry ? entry->GetType() : none_Type; // Don't print the error twice.

			outputRule(20);
			parse_EXPRESSION_i(type);
			if (!matchKinds(variable_Kind | constant_Kind, kind))
			{
				char buffer[1000] = { '\0' };

				sprintf(buffer, "Semantic Kind Mismatch Error: Only a variable or constant can be used in this context. '%s' is niether, at [%d, %d] (line, column).",
					t->lexeme, t->lineNumber, t->columnNumber);
				outputLine(buffer);
			}
		}

		break;
	case leftbracket_Token:
		outputRule(21);
		type = parse_EXPRESSION();
		if (match(rightbracket_Token, followList))
		{
			type = parse_EXPRESSION_i(type);
		}
		break;
	default:
		outputLexicalError(t, expectedTokenTypes);
		gotoFollow(followList);
		break;
	}

	return type;
}

eType parse_EXPRESSION_i(eType i_Type)
{
	eTokenType followList[] = { rel_op_Token, then_Token, od_Token, rightbracket_Token, ar_op_Token, 
		semicolon_Token, none_Token };
	Token *t = nextToken();
	eType type = i_Type;

	if (t->type == ar_op_Token)
	{
		outputRule(22);
		type = parse_EXPRESSION();
		// In case we have int and real, convert the result to real
		if (type != i_Type)
		{
			type = real_Type;
		}

		type = parse_EXPRESSION_i(type);
		nextToken();
	}
	if (existsInFollow(t->type, followList))
	{
		outputRule(23);
		backToken();
	}
	else
	{
		outputLexicalError(t, followList);
		gotoFollow(followList);
	}

	return type;
}

eType parse_CONDITION()
{
	eTokenType followList[] = { then_Token, od_Token, none_Token };
	eType type = none_Type;

	outputRule(24);
	parse_EXPRESSION();
	if (match(rel_op_Token, followList))
	{
		type = boolean_Type;
		parse_EXPRESSION();
	}

	return type;
}